<?php
require __DIR__ . '/database/mysql_db.php';
$config = require __DIR__ . '/config/server.php';
$db = new MySQLPDO();
$msg_path = __DIR__ . '/public/cache/msg';
$host = $config['ip'];
//指定监听的端口，注意该端口不能与现有应用的端口冲突
$port = $config['dk'];

$null = null;
$_n = "\n";
// 创建Socket。AF_INET：代表通信时使用IPv4协议；SOCK_STREAM：代表传输的数据是二进制流数据；SOL_TCP：代表底层使用的协议是TCP
$socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
// 指定Socket相应的属性。SOL_SOCKET：设定协议的等级；SO_REUSEADDR：设置端口释放之后可以立即被使用
socket_set_option($socket, SOL_SOCKET, SO_REUSEADDR, 1);
// 绑定端口
socket_bind($socket, 0, $port);
// 监听端口
socket_listen($socket);
// 声明一个数组，用于存放所有的客户端连接
$clients = array(
    $socket
);

echo '通讯服务启动成功! ' . $host . ':' . $port . "\n";

while (true) {
    $changed_socket = $clients;
    // 在当前数组中获取活跃的Socket连接，即当前正在发送请求的连接或正在传输数据的连接等
    socket_select($changed_socket, $null, $null, 0, 10);
    // 判断当前的Socket是否为活跃的Socket，如果是，说明客户端在请求连接
    if (in_array($socket, $changed_socket)) {
        // 接受连接
        $socket_new = socket_accept($socket);
        socket_getpeername($socket_new, $pip);
        $clients[] = $socket_new;

        echo "客户端连接 " . $pip . "\n";

        // 发送握手信息
        $header = socket_read($socket_new, 52000);
        perform_handshaking($header, $socket_new, $host, $port);
        // 在连接成功后，当前Socket要从活跃Socket列表中删除，否则会陷入死循环
        $key = array_search($socket, $changed_socket);
        unset($changed_socket[$key]);
    } else {
        // 不是新连接，是客户端在发送数据
        // 服务器开始读取客户端发送的数据
        $received_text = '';
        foreach ($changed_socket as $v) {
            while (socket_recv($v, $buf, 52000, 0) >= 1) {
                // 解包数据
                $received_text = unmask($buf);
                $msgJson = json_decode($received_text);
                //判读是否为对象 验证消息完整性
                if (is_object($msgJson)) {
                    $message = msg_pro_exe($db, $msgJson);
                } else {
                    $info = json_encode($msgJson);
                    echo "解析失败:" . $info . "\n";
                    // if ($info == 'null' && strlen($received_text) > 52000) {
                    //     echo "消息内容超出最大限制:" . strlen($received_text) . "\n";
                    //     // 将数据进行编码
                    //     $message = json_encode([
                    //         'error' => '消息内容超出最大限制', //错误提示
                    //     ]);
                    // }
                }
                // 广播编码后的数据。服务器进行广播的消息会触发客户端的onmessage事件
                send_message($message);
                // 跳出foreach循环
                break 2;
            }
            // 删除已经关闭的Socket
            $buf = @socket_read($v, 1024, PHP_NORMAL_READ);
            if ($buf === false) {
                $key = array_search($v, $clients);
                socket_getpeername($v, $ip);
                unset($clients[$key]);
                $msg = json_encode([
                    'type' => 'system',
                    'content' => $ip . "已经下线。"
                ]);
                send_message($msg);
            }
        }
    }
}

// 发送消息的方法
function send_message($msg)
{
    global $clients;
    //判断长度
    $len = strlen($msg);
    if ($len > 65536) {
        echo "长度过程" . "\n";
        $n = intval($len / 51200);
        $s = 0;
        for ($i = 0; $i <= $n; $i++) {
            $data = substr(($msg), $s, 51200);
            foreach ($clients as $changed_socket) {
                @socket_write($changed_socket, mask($data), 65536);
            }
            $s = $s + 51200;
        }
        return true;
    } else {
        echo "标准长度" . "\n";
        foreach ($clients as $changed_socket) {
            // @socket_write($changed_socket, $msg, strlen($msg));
            @socket_write($changed_socket, mask($msg), 65536);
        }
        return true;
    }
}

// 以下3个函数，我们只需了解总体结构即可。在有需要时可以直接使用
// 解码数据。服务器解码客户端发送过来的数据
function unmask($text)
{
    $length = ord($text[1]) & 127;
    if ($length == 126) {
        $masks = substr($text, 4, 4);
        $data = substr($text, 8);
    } elseif ($length == 127) {
        $masks = substr($text, 10, 4);
        $data = substr($text, 14);
    } else {
        $masks = substr($text, 2, 4);
        $data = substr($text, 6);
    }
    $text = "";
    for ($i = 0; $i < strlen($data); ++$i) {
        $text .= $data[$i] ^ $masks[$i % 4];
    }
    return $text;
}

// 编码数据。在服务器向客户端发送数据时需要将数据打包
function mask($text)
{
    $b1 = 0x80 | (0x1 & 0x0f);
    $length = strlen($text);
    $length . "\n";
    if ($length <= 125) {
        $header = pack('CC', $b1, $length);
    } elseif ($length > 125 && $length < 65536) {
        $header = pack('CCn', $b1, 126, $length);
    } elseif ($length >= 65536) {
        $header = pack('CCN', $b1, 127, $length);
    }
    return $header . $text;
}

// 握手的逻辑。客户端与服务端相互识别的过程
function perform_handshaking($receved_header, $client_conn, $host, $port)
{
    $headers = array();
    $lines = preg_split("/\r\n/", $receved_header);
    foreach ($lines as $line) {
        $line = chop($line);
        if (preg_match('/\A(\S+): (.*)\z/', $line, $matches)) {
            $headers[$matches[1]] = $matches[2];
        }
    }
    $secKey = $headers['Sec-WebSocket-Key'];
    $secAccept = base64_encode(pack('H*', sha1($secKey . '258EAFA5-E914-47DA-95CA-C5AB0DC85B11')));
    $upgrade = "HTTP/1.1 101 Web Socket Protocol Handshake\r\n" . "Upgrade: websocket\r\n" . "Connection: Upgrade\r\n" . "WebSocket-Origin: $host\r\n" . "Sec-WebSocket-Accept:$secAccept\r\n\r\n";
    socket_write($client_conn, $upgrade, strlen($upgrade));
}

//验证密码
function checkpaswd($db, $id, $pass = '')
{
    $sql = "SELECT * FROM ly_room WHERE room_id = " . $id;
    $data = $db->fetchRow($sql);
    if ($data['room_pwd'] == $pass) {
        return true;
    } else {
        return false;
    }
}

//消息查询
function getnews_data($db, $id, $name = null)
{
    $sql = 'SELECT ly_news.*,ly_user.`user_name`,ly_user.`info_id`,ly_info.* FROM ly_news INNER JOIN ly_user ON ly_news.username = ly_user.`user_name` INNER JOIN ly_info ON ly_user.`info_id` = ly_info.`uid` WHERE `room_id` = ' . $id . ' ORDER BY mid DESC LIMIT 0, 25 ;';

    $data = $db->fetchAll($sql);

    if ($data) {
        foreach ($data as $key => $value) {
            if ($name == $value['username']) {
                // $us = 'support';
                $us = 'visitor';
            } else {
                // $us = 'visitor';
                $us = 'support';
            }
            $news[$key] = [
                'user' => $us, //判断自我
                'icon' => $value['icon'], //头像
                'nick' => $value['nickname'], //昵称
                'name' => $value['username'], //用户名
                'room' => $value['room_id'], //房间号
                'content' => $value['content'], //消息内容
                'type' => $value['m_type'], //消息类型
                'time' => $value['m_time'], //消息类型
            ];
        }
    } else {
        $news = [];
    }
    return array_reverse($news);
}

//验证房间处罚
function punish($db, $room, $name, $code = 0)
{
    $sql = "SELECT * FROM ly_punish WHERE user_id = '" . $name . "' AND room_id = " . $room . " AND result > " . $code . ";";
    $data = $db->fetchAll($sql);
    if (count($data) > 0) {
        return false;
    } else {
        return true;
    }
}

//事件写入
function msg_log($db, $data)
{
    //数据库语句
    $sql = 'INSERT INTO ly_news (username,room_id,content,m_type) VALUES(?,?,?,?)';
    $db->query($sql, $data);
    //取出最后插入ID判断是否插入成功
    if ($db->lastInsertId() > 0) {
        echo '消息写入成功：' . json_encode($data) . " 事件时间：" . date('Y-m-d H:i:s') . "\n";
    } else {
        echo '消息写入失败：' . json_encode($data) . " 事件时间：" . date('Y-m-d H:i:s') . "\n";
    }
}

//消息预处理
function msg_pro_exe($db, $msgJson)
{

    global $msg_path;
    // 读取数据
    $icon = trim(isset($msgJson->icon) ? $msgJson->icon : null); //用户头像
    $nick = trim(isset($msgJson->nick) ? $msgJson->nick : null); //用户昵称
    $name = trim(isset($msgJson->name) ? $msgJson->name : null); //用户名
    $room = trim(isset($msgJson->room) ? $msgJson->room : 0); //房间号
    $content = isset($msgJson->content) ? $msgJson->content : null; //消息内容
    $type = trim(isset($msgJson->type) ? $msgJson->type : null); //消息类型
    $pass = trim(isset($msgJson->pass) ? $msgJson->pass : ''); //房间密码


    //时间
    if ($type == 'pic') {
        $time = $msgJson->time;
        // $content = base64_image_content($content, $msg_path);
    } else {
        $time = date('Y-m-d 星期w H:i:s', time());
    }

    //判断用户发送
    if ($content == null) {
        if (checkpaswd($db, $room, $pass) === true) {
            if (punish($db, $room, $name, 1) === true) {
                //历史消息
                $news = getnews_data($db, $room, $name);
                // 将数据进行编码
                $message = json_encode([
                    'name' => $name, //用户名
                    'room' => $room, //房间号
                    "nick" => $nick, //昵称
                    'news' => $news, //消息
                    'time' => $time, //消息类型
                    'list' => true
                ]);
            } else {
                $message = json_encode([
                    'error' => '你已被房主拉入黑名单,无法接收任何消息。', //错误提示
                    'name' => $name,
                    'room' => $room
                ]);
            }
        } else {
            $message = json_encode([
                'error' => '请输入房间密码', //错误提示
                'name' => $name,
                'room' => $room,
                'tips' => 'pass'
            ]);
        }
    } else {
        if (checkpaswd($db, $room, $pass) === true) {
            //消息写入处理
            if ($type != null && $content != null && $room != null && $name != null) {
                if (punish($db, $room, $name) === true) {
                    $r_data = [$name, $room, $content, $type];
                    msg_log($db, $r_data);
                    // 将数据进行编码
                    $message = json_encode([
                        // 'user' => $user, //头像
                        'icon' => $icon, //头像
                        'nick' => $nick, //昵称
                        'name' => $name, //用户名
                        'room' => $room, //房间号
                        'content' => $content, //消息内容
                        'type' => $type, //消息类型
                        'time' => $time, //消息类型
                    ]);
                } else {
                    $message = json_encode([
                        'error' => '你已被房主禁言,暂时无法发言。', //错误提示
                        'name' => $name,
                        'room' => $room
                    ]);
                }
            } else {
                $message = json_encode([]);
            }
        } else {
            $message = json_encode([
                'error' => '请输入房间密码', //错误提示
                'name' => $name,
                'room' => $room,
                'tips' => 'pass'
            ]);
        }
    }
    return $message;
}



/**
 * [将Base64图片转换为本地图片并保存]
 * @param  [Base64] $base64_image_content [要保存的Base64]
 * @param  [目录] $path [要保存的路径]
 */
function base64_image_content($base64_image_content, $path)
{
    //匹配出图片的格式
    if (preg_match('/^(data:\s*image\/(\w+);base64,)/', $base64_image_content, $result)) {
        $type = $result[2];
        $file_path = date('Ymd', time());
        $new_file = $path . "/" . $file_path . "/";
        if (!file_exists($new_file)) {
            //检查是否有该文件夹，如果没有就创建，并给予最高权限
            mkdir($new_file, 0700);
        }
        $file_name = time();
        $new_file = $new_file . $file_name . ".{$type}";
        $file_url = "/cache/msg/" . $file_path . '/' . $file_name . ".{$type}";
        if (file_put_contents($new_file, base64_decode(str_replace($result[1], '', $base64_image_content)))) {
            return $file_url;
        } else {
            return false;
        }
    } else {
        return false;
    }
}
